package cn.kbyue.security.config;

import cn.kbyue.security.security.CustomAccessDecisionManager;
import cn.kbyue.security.security.CustomAccessDeniedHandler;
import cn.kbyue.security.security.CustomAuthenticationEntryPoint;
import cn.kbyue.security.security.CustomAuthenticationProcessingFilter;
import cn.kbyue.security.security.CustomAuthenticationProvider;
import cn.kbyue.security.security.CustomFilterInvocationSecurityMetadataSource;
import cn.kbyue.security.security.CustomLogoutSuccessHandler;
import cn.kbyue.security.security.CustomPermissionEvaluator;
import cn.kbyue.security.security.CustomTokenFilter;
import javax.annotation.Resource;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

/**
 * springsecurity 认证 和 鉴权 配置
 *
 * 依赖: @DependsOn("securityProperty") 配置文件中的自定义
 *
 * @author xl
 * @date 2020/4/19 18:09
 */
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * token校验
     */
    @Resource
    private CustomTokenFilter customTokenFilter;

    /**
     * 自定义用户登录信息
     */
    @Resource
    private CustomAuthenticationProvider customAuthenticationProvider;

    /**
     * 自定义认证处理拦截器  用户登录的形式 登录的接口等
     */
    @Resource
    private CustomAuthenticationProcessingFilter customAuthenticationProcessingFilter;

    /**
     * 录入访问url需要的角色信息
     */
    @Resource
    private CustomFilterInvocationSecurityMetadataSource customFilterInvocationSecurityMetadataSource;

    /**
     * 校验用户是否是访问 url 的角色
     */
    @Resource
    private CustomAccessDecisionManager customAccessDecisionManager;

    /**
     * 401 未登录 处理器
     */
    @Resource
    private CustomAuthenticationEntryPoint customAuthenticationEntryPoint;

    /**
     * 403 无权限 处理器
     */
    @Resource
    private CustomAccessDeniedHandler customAccessDeniedHandler;

    /**
     * 注销成功 处理器
     */
    @Resource
    private CustomLogoutSuccessHandler customLogoutSuccessHandler;

    /**
     * 注入自定义 PermissionEvaluator 实现hasPermission注解逻辑
     */
    @Bean
    public DefaultWebSecurityExpressionHandler defaultWebSecurityExpressionHandler() {
        DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
        handler.setPermissionEvaluator(new CustomPermissionEvaluator());
        return handler;
    }

    /**
     * 配置登录验证逻辑
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) {
        // 启动自定义登录逻辑
        auth.authenticationProvider(customAuthenticationProvider);
    }

    /**
     * 去除角色默认的 ROLE_ 前缀
     */
    @Bean
    public GrantedAuthorityDefaults roleVoter() {
        return new GrantedAuthorityDefaults("");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // url 是否鉴权
        http.authorizeRequests()
                // 允许匿名访问的 url
                .antMatchers("/login", "/login/key").permitAll()
                // 其他接口都需要鉴权
                .anyRequest().authenticated()
                .and()
                .logout()
                .logoutUrl("/logout")
                .logoutSuccessHandler(customLogoutSuccessHandler);

        // 杂项配置
        http.authorizeRequests()
                .and()
                // 开启跨域
                .cors()
                .and()
                // 取消跨站请求伪造防护
                .csrf().disable()
                // 禁用缓存 ? 加disabled() ?
                .headers()
                .cacheControl()
                .and()
                // 防止iframe 造成跨域
                .frameOptions().disable();

        // 异常处理
        http.exceptionHandling()
                // 401 未登录
                .authenticationEntryPoint(customAuthenticationEntryPoint)
                // 403 无权限
                .accessDeniedHandler(customAccessDeniedHandler);

        // 设定 session 为无状态模式
        http.sessionManagement()
                // 单个用户只能创建一个session，也就只能在服务器登录一次
                .maximumSessions(1)
                .and()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        // 对 需要鉴权的请求 进行的自定义设置
        http.authorizeRequests()
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                        // 设定需要权限校验的 url 列表
                        o.setSecurityMetadataSource(customFilterInvocationSecurityMetadataSource);
                        // 检验 url 用户是否可以访问 (角色角度)
                        o.setAccessDecisionManager(customAccessDecisionManager);
                        return o;
                    }
                });

        // 自定义过滤器认证用户名密码
        http.addFilterAt(customAuthenticationProcessingFilter, UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(customTokenFilter, BasicAuthenticationFilter.class);

        // 其他可添加配置
        //        http.authorizeRequests()
        //                // 标识只能在 服务器本地ip[127.0.0.1或localhost] 访问`/home`接口，其他ip地址无法访问
        //                .antMatchers("/home").hasIpAddress("127.0.0.1")
        //                // 只有admin 角色才能访问 /admin
        //                .antMatchers("/admin").hasRole("admin")
        //                .and()
        //                // 由 CustomAuthenticationProcessingFilter 设定登录的接口和处理器
        //                .formLogin()
        //                .loginProcessingUrl("/login")
        //                .successHandler(customAuthenticationSuccessHandler)
        //                .failureHandler(customAuthenticationFailureHandler)
        //                // 自动登录 - cookie储存方式
        //                .and().rememberMe();
    }

    @Override
    public void configure(WebSecurity web) {
        web.ignoring().antMatchers(HttpMethod.GET, "/favicon.ico");
        web.ignoring().antMatchers("/api/test/**");
    }

    /**
     * 使用BCryptPasswordEncoder hash 算法，用于算密码
     */
    @Bean(name = "bCryptPasswordEncoder")
    public PasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
